/*
 * Copyright (C)2020 Teluu Inc. (http://www.teluu.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <pjmedia-codec/ohos_codec_vid.h>
#include <pjmedia-codec/h264_packetizer.h>
#include <pjmedia/vid_codec_util.h>
#include <pjmedia/errno.h>
#include <pj/atomic_queue.h>
#include <pj/log.h>

#if defined(PJMEDIA_HAS_OHOS_AVCODEC) && PJMEDIA_HAS_OHOS_AVCODEC != 0 && defined(PJMEDIA_HAS_VIDEO) &&                \
    (PJMEDIA_HAS_VIDEO != 0)

#include "ohosvideodecoder.h"
#include "ohosvideoencoder.h"
#include "ohosdec_common.h"

#define THIS_FILE "ohos_codec_vid.cpp"

#define DEFAULT_WIDTH 352
#define DEFAULT_HEIGHT 288

#define DEFAULT_FPS 15
#define FPS_25 25
#define FPS_30 30

#define DEFAULT_AVG_BITRATE 256000
#define DEFAULT_MAX_BITRATE 256000
#define CLOCK_RATE 90000
#define MAGIC_ZERO 0
#define MAGIC_ONE 1
#define MAGIC_TWO 2
#define MAGIC_THIRD 3
#define MAGIC_FOUR 4

#define TIMEOUT 8

#define SPS_PPS_BUF_SIZE 64

#define MAX_RX_WIDTH 1280
#define MAX_RX_HEIGHT 800

#define KEYFRAME_INTERVAL 1000

#define OHOS_MED_H264_PT PJMEDIA_RTP_PT_OHOS_AVCODEC_H264

static pj_status_t ohos_media_test_alloc(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info);
static pj_status_t ohos_media_default_attr(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info,
                                          pjmedia_vid_codec_param *attr);
static pj_status_t ohos_media_enum_info(pjmedia_vid_codec_factory *factory, unsigned *count,
                                       pjmedia_vid_codec_info codecs[]);
static pj_status_t ohos_media_alloc_codec(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info,
                                         pjmedia_vid_codec **p_codec);
static pj_status_t ohos_media_dealloc_codec(pjmedia_vid_codec_factory *factory, pjmedia_vid_codec *codec);


static pj_status_t ohos_media_codec_init(pjmedia_vid_codec *codec, pj_pool_t *pool);
static pj_status_t ohos_media_codec_open(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *param);
static pj_status_t ohos_media_codec_close(pjmedia_vid_codec *codec);
static pj_status_t ohos_media_codec_modify(pjmedia_vid_codec *codec, const pjmedia_vid_codec_param *param);
static pj_status_t ohos_media_codec_get_param(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *param);
static pj_status_t ohos_media_codec_encode_begin(pjmedia_vid_codec *codec, const pjmedia_vid_encode_opt *opt,
                                                const pjmedia_frame *input, unsigned out_size, pjmedia_frame *output,
                                                pj_bool_t *has_more);
static pj_status_t ohos_media_codec_encode_more(pjmedia_vid_codec *codec, unsigned out_size, pjmedia_frame *output,
                                               pj_bool_t *has_more);
static pj_status_t ohos_media_codec_decode(pjmedia_vid_codec *codec, pj_size_t count, pjmedia_frame packets[],
                                          unsigned out_size, pjmedia_frame *output);

static pjmedia_vid_codec_op ohos_media_codec_op = {
    &ohos_media_codec_init,
    &ohos_media_codec_open,
    &ohos_media_codec_close,
    &ohos_media_codec_modify,
    &ohos_media_codec_get_param,
    &ohos_media_codec_encode_begin,
    &ohos_media_codec_encode_more,
    &ohos_media_codec_decode,
    NULL
};

static pjmedia_vid_codec_factory_op ohos_media_factory_op = {
    &ohos_media_test_alloc,
    &ohos_media_default_attr,
    &ohos_media_enum_info,
    &ohos_media_alloc_codec,
    &ohos_media_dealloc_codec
};

static struct ohos_media_factory {
    pjmedia_vid_codec_factory base;
    pjmedia_vid_codec_mgr *mgr;
    pj_pool_factory *pf;
    pj_pool_t *pool;
} ohos_media_factory;

typedef struct h264_codec_data {
    pjmedia_h264_packetizer *pktz;

    pj_uint8_t enc_sps_pps_buf[SPS_PPS_BUF_SIZE];
    unsigned enc_sps_pps_len;
    pj_bool_t enc_sps_pps_ex;

    pj_uint8_t *dec_sps_buf;
    unsigned dec_sps_len;
    pj_uint8_t *dec_pps_buf;
    unsigned dec_pps_len;
} h264_codec_data;

typedef struct ohos_media_codec_data {
    pj_pool_t *pool;
    pj_uint8_t codec_idx;
    pjmedia_vid_codec_param *prm;
    pj_bool_t whole;
    void *ex_data;

    /* Encoder state */
    std::unique_ptr<ohos::VideoEncoder> enc = nullptr;
    pj_uint8_t *enc_frame_whole;
    unsigned enc_frame_size;
    unsigned enc_processed;
    OH_AVCodecBufferAttr enc_buf_info;
    int enc_output_buf_idx;
    int enc_stride_len;

    /* Decoder state */
    std::unique_ptr<ohos::VideoDecoder> dec = nullptr;
    pj_uint8_t *dec_buf;
    pj_uint8_t *dec_input_buf;
    unsigned dec_input_buf_len;
    pj_size_t dec_input_buf_max_size;
    unsigned dec_has_output_frame;
    unsigned dec_buf_size;
    pj_bool_t format_changed;
    int width;
    int height;
    int stride;
} ohos_media_codec_data;

/* Custom callbacks. */
typedef pj_status_t (*open_cb)(ohos_media_codec_data *ohos_media_data);

typedef pj_status_t (*process_encode_cb)(ohos_media_codec_data *ohos_media_data);

typedef pj_status_t (*encode_more_cb)(ohos_media_codec_data *ohos_media_data, unsigned out_size, pjmedia_frame *output,
                                      pj_bool_t *has_more);

typedef pj_status_t (*decode_cb)(pjmedia_vid_codec *codec, pj_size_t count, pjmedia_frame packets[], unsigned out_size,
                                 pjmedia_frame *output);


static pj_status_t open_h264(ohos_media_codec_data *ohos_media_data);
static pj_status_t process_encode_h264(ohos_media_codec_data *ohos_media_data);
static pj_status_t encode_more_h264(ohos_media_codec_data *ohos_media_data, unsigned out_size, pjmedia_frame *output,
                                    pj_bool_t *has_more);
static pj_status_t decode_h264(pjmedia_vid_codec *codec, pj_size_t count, pjmedia_frame packets[], unsigned out_size,
                               pjmedia_frame *output);


static pj_str_t H264_encoder[] = {{(char *)"OHOS.h264.encoder\0", 17}};
static pj_str_t H264_decoder[] = {{(char *)"OHOS.h264.decoder\0", 17}};

static struct ohos_media_codec {
    int enabled;
    const char *name; 
    const char *description;
    const char *mime_type;
    pj_str_t *encoder_name;
    pj_str_t *decoder_name;
    pj_uint8_t pt;
    pjmedia_format_id fmt_id;
    pj_uint32_t keyframe_interval;
    open_cb open_codec;
    process_encode_cb process_encode;
    encode_more_cb encode_more;
    decode_cb decode;
    pjmedia_codec_fmtp dec_fmtp;
} ohos_media_codec[] = {
    {
        MAGIC_ZERO,
        "H264",
        "OHOS MediaCodec H264 codec",
        "video/avc",
        NULL,
        NULL,
        OHOS_MED_H264_PT,
        PJMEDIA_FORMAT_H264,
        KEYFRAME_INTERVAL,
        &open_h264,
        &process_encode_h264,
        &encode_more_h264,
        &decode_h264,
        {
            MAGIC_TWO,
            {
                {{(char *)"profile-level-id", 16}, {(char *)"42e01e", 6}},
                {{(char *)" packetization-mode", 19}, {(char *)"1", 1}}
            }
        }
    },
};

static pj_status_t configure_encoder(ohos_media_codec_data *ohos_media_data)
{
    pjmedia_vid_codec_param *param = ohos_media_data->prm;
    std::unique_ptr<ohos::CodecData> codecData = std::make_unique<ohos::CodecData>();
    std::unique_ptr<ohos::VideoFormat> formatInfo = std::make_unique<ohos::VideoFormat>();

    formatInfo->width = param->enc_fmt.det.vid.size.w;
    formatInfo->height = param->enc_fmt.det.vid.size.h;
    formatInfo->codecMime = "video/avc";
    if (param->enc_fmt.det.vid.fps.num >= param->enc_fmt.det.vid.fps.denum && param->enc_fmt.det.vid.fps.denum > 0) {
        formatInfo->frameRate = param->enc_fmt.det.vid.fps.num / param->enc_fmt.det.vid.fps.denum;
    }
    formatInfo->bit_rate = param->enc_fmt.det.vid.avg_bps;
    formatInfo->keyFrameInterval = KEYFRAME_INTERVAL;
    formatInfo->pixelFormat = AV_PIXEL_FORMAT_YUVI420;
    formatInfo->lowLatency = true;
    ohos_media_data->enc = std::make_unique<ohos::VideoEncoder>(std::move(codecData), std::move(formatInfo));
    if (ohos_media_data->enc->Create() != 0) {
        return PJ_EINVAL;
    }

    if (ohos_media_data->enc->Config() != 0) {
        return PJ_EINVAL;
    }

    if (ohos_media_data->enc->Start() != 0) {
        return PJ_EINVAL;
    }
    return PJ_SUCCESS;
}

static pj_status_t configure_decoder(ohos_media_codec_data *ohos_media_data)
{
    pjmedia_vid_codec_param *param = ohos_media_data->prm;
    std::unique_ptr<ohos::CodecData> codecData = std::make_unique<ohos::CodecData>();
    std::unique_ptr<ohos::VideoFormat> formatInfo = std::make_unique<ohos::VideoFormat>();
    formatInfo->width = param->dec_fmt.det.vid.size.w;
    formatInfo->height = param->dec_fmt.det.vid.size.h;
    formatInfo->codecMime = "video/avc";
    formatInfo->pixelFormat = AV_PIXEL_FORMAT_NV21;
    formatInfo->lowLatency = true;

    ohos_media_data->dec = std::make_unique<ohos::VideoDecoder>(std::move(codecData), std::move(formatInfo));
    ohos_media_data->dec->fmtChanged_ = &ohos_media_data->format_changed;
    if (ohos_media_data->dec->Create() != 0) {
        return PJ_EINVAL;
    }

    if (ohos_media_data->dec->Config() != 0) {
        return PJ_EINVAL;
    }

    if (ohos_media_data->dec->Start() != 0) {
        return PJ_EINVAL;
    }

    ohos_media_data->dec_input_buf_max_size = ohos_media_data->dec->GetInputBufferSize();
    if (ohos_media_data->dec_input_buf_max_size == 0) {
        return PJ_FALSE;
    }
    ohos_media_data->dec_input_buf = (pj_uint8_t*)pj_pool_alloc(ohos_media_data->pool, ohos_media_data->dec_input_buf_max_size);
    return PJ_SUCCESS;
}

extern "C" pj_status_t pjmedia_codec_ohos_codec_vid_init(pjmedia_vid_codec_mgr *mgr, pj_pool_factory *pf)
{
    const pj_str_t h264_name = {(char *)"H264", MAGIC_FOUR};
    pj_status_t status;
    if (ohos_media_factory.pool != NULL) {
        return PJ_SUCCESS;
    }

    if (!mgr)
        mgr = pjmedia_vid_codec_mgr_instance();
    PJ_ASSERT_RETURN(mgr, PJ_EINVAL);

    ohos_media_factory.base.op = &ohos_media_factory_op;
    ohos_media_factory.base.factory_data = NULL;
    ohos_media_factory.mgr = mgr;
    ohos_media_factory.pf = pf;
    ohos_media_factory.pool = pj_pool_create(pf, "ohos_media_vid_factory", 256, 256, NULL);
    if (!ohos_media_factory.pool)
        return PJ_ENOMEM;

    status = pjmedia_sdp_neg_register_fmt_match_cb(&h264_name, &pjmedia_vid_codec_h264_match_sdp);
    if (status != PJ_SUCCESS)
        goto on_error;

    status = pjmedia_vid_codec_mgr_register_factory(mgr, &ohos_media_factory.base);
    if (status != PJ_SUCCESS)
        goto on_error;

    PJ_LOG(4, (THIS_FILE, "OHOS Media Codec initialized"));
    return PJ_SUCCESS;

on_error:
    pj_pool_release(ohos_media_factory.pool);
    ohos_media_factory.pool = NULL;
    return status;
}

PJ_DEF(pj_status_t) pjmedia_codec_ohos_media_vid_deinit(void)
{
    pj_status_t status = PJ_SUCCESS;
    if (ohos_media_factory.pool == NULL) {
        return PJ_SUCCESS;
    }

    status = pjmedia_vid_codec_mgr_unregister_factory(ohos_media_factory.mgr, &ohos_media_factory.base);

    pj_pool_release(ohos_media_factory.pool);
    ohos_media_factory.pool = NULL;
    return status;
}

static pj_status_t ohos_media_test_alloc(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info)
{
    unsigned i;

    PJ_ASSERT_RETURN(factory == &ohos_media_factory.base, PJ_EINVAL);

    for (i = 0; i < PJ_ARRAY_SIZE(ohos_media_codec); ++i) {
        if (ohos_media_codec[i].enabled && info->pt == ohos_media_codec[i].pt &&
            (info->fmt_id == ohos_media_codec[i].fmt_id)) {
            return PJ_SUCCESS;
        }
    }
    return PJMEDIA_CODEC_EUNSUP;
}

static pj_status_t ohos_media_default_attr(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info,
                                          pjmedia_vid_codec_param *attr)
{
    unsigned i;
    PJ_ASSERT_RETURN(factory == &ohos_media_factory.base, PJ_EINVAL);
    PJ_ASSERT_RETURN(info && attr, PJ_EINVAL);

    for (i = 0; i < PJ_ARRAY_SIZE(ohos_media_codec); ++i) {
        if (ohos_media_codec[i].enabled && info->pt != 0 && (info->fmt_id == ohos_media_codec[i].fmt_id)) {
            break;
        }
    }

    if (i == PJ_ARRAY_SIZE(ohos_media_codec))
        return PJ_EINVAL;

    pj_bzero(attr, sizeof(pjmedia_vid_codec_param));

    attr->dir = PJMEDIA_DIR_ENCODING_DECODING;
    attr->packing = PJMEDIA_VID_PACKING_PACKETS;

    pjmedia_format_init_video(&attr->enc_fmt, info->fmt_id, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FPS, 1);

    pjmedia_format_init_video(&attr->dec_fmt, PJMEDIA_FORMAT_I420, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FPS, 1);

    attr->dec_fmtp = ohos_media_codec[i].dec_fmtp;

    attr->enc_fmt.det.vid.avg_bps = DEFAULT_AVG_BITRATE;
    attr->enc_fmt.det.vid.max_bps = DEFAULT_MAX_BITRATE;

    attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
    return PJ_SUCCESS;
}

void add_codec(struct ohos_media_codec *codec, unsigned *count, pjmedia_vid_codec_info *info)
{
    info[*count].fmt_id = codec->fmt_id;
    info[*count].pt = codec->pt;
    info[*count].encoding_name = pj_str((char *)codec->name);
    info[*count].encoding_desc = pj_str((char *)codec->description);

    info[*count].clock_rate = CLOCK_RATE;
    info[*count].dir = PJMEDIA_DIR_ENCODING_DECODING;
    info[*count].dec_fmt_id_cnt = 1;
    info[*count].dec_fmt_id[0] = PJMEDIA_FORMAT_I420;
    info[*count].packings = PJMEDIA_VID_PACKING_PACKETS;
    info[*count].fps_cnt = MAGIC_THIRD;
    info[*count].fps[MAGIC_ZERO].num = DEFAULT_FPS;
    info[*count].fps[MAGIC_ZERO].denum = MAGIC_ONE;
    info[*count].fps[MAGIC_ONE].num = FPS_25;
    info[*count].fps[MAGIC_ONE].denum = MAGIC_ONE;
    info[*count].fps[MAGIC_TWO].num = FPS_30;
    info[*count].fps[MAGIC_TWO].denum = MAGIC_ONE;
    ++*count;
}

static void get_codec_name(pj_bool_t is_enc, pj_bool_t prio, pjmedia_format_id fmt_id, pj_str_t **codec_name,
                           unsigned *codec_num)
{
    *codec_num = 0;
    switch (fmt_id) {
        case PJMEDIA_FORMAT_H264: {
            if (is_enc) {
                *codec_name = &H264_encoder[MAGIC_ZERO];
                *codec_num = PJ_ARRAY_SIZE(H264_encoder);
            } else {
                *codec_name = &H264_decoder[MAGIC_ZERO];
                *codec_num = PJ_ARRAY_SIZE(H264_decoder);
            }
            break;
        }
        default:
            break;
    }
}

static pj_status_t ohos_media_enum_info(pjmedia_vid_codec_factory *factory, unsigned *count,
                                        pjmedia_vid_codec_info info[])
{
    unsigned i, max;

    PJ_ASSERT_RETURN(info && *count > 0, PJ_EINVAL);
    PJ_ASSERT_RETURN(factory == &ohos_media_factory.base, PJ_EINVAL);
    max = *count;

    for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(ohos_media_codec) && *count < max; ++i) {
        pj_str_t *enc_name = NULL;
        unsigned num_enc;
        pj_str_t *dec_name = NULL;
        unsigned num_dec;

        get_codec_name(PJ_TRUE, PJ_TRUE, ohos_media_codec[i].fmt_id, &enc_name, &num_enc);
        get_codec_name(PJ_FALSE, PJ_TRUE, ohos_media_codec[i].fmt_id, &dec_name, &num_dec);
        ohos_media_codec[i].encoder_name = enc_name;
        ohos_media_codec[i].decoder_name = dec_name;
        add_codec(&ohos_media_codec[*count], count, info);
        ohos_media_codec[i].enabled = PJ_TRUE;
    }
    return PJ_SUCCESS;
}

static void create_codec(struct ohos_media_codec_data *ohos_media_data)
{
    char *enc_name;
    char *dec_name;
    if (!ohos_media_codec[ohos_media_data->codec_idx].encoder_name ||
        !ohos_media_codec[ohos_media_data->codec_idx].decoder_name) {
        return;
    }

    enc_name = ohos_media_codec[ohos_media_data->codec_idx].encoder_name->ptr;
    dec_name = ohos_media_codec[ohos_media_data->codec_idx].decoder_name->ptr;

    PJ_LOG(4, (THIS_FILE, "Created encoder: %s, decoder: %s", enc_name, dec_name));
}

static pj_status_t ohos_media_alloc_codec(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info,
                                          pjmedia_vid_codec **p_codec)
{
    pj_pool_t *pool;
    pjmedia_vid_codec *codec;
    ohos_media_codec_data *ohos_media_data;
    int i, idx;

    PJ_ASSERT_RETURN(factory == &ohos_media_factory.base && info && p_codec, PJ_EINVAL);

    idx = -1;
    for (i = 0; i < PJ_ARRAY_SIZE(ohos_media_codec); ++i) {
        if ((info->fmt_id == ohos_media_codec[i].fmt_id) && (ohos_media_codec[i].enabled)) {
            idx = i;
            break;
        }
    }
    if (idx == -1) {
        *p_codec = NULL;
        return PJMEDIA_CODEC_EFAILED;
    }

    *p_codec = NULL;
    pool = pj_pool_create(ohos_media_factory.pf, "ohmedvid%p", 512, 512, NULL);
    if (!pool)
        return PJ_ENOMEM;

    /* codec instance */
    codec = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec);
    codec->factory = factory;
    codec->op = &ohos_media_codec_op;

    /* codec data */
    ohos_media_data = PJ_POOL_ZALLOC_T(pool, ohos_media_codec_data);
    ohos_media_data->pool = pool;
    ohos_media_data->codec_idx = idx;
    codec->codec_data = ohos_media_data;

    create_codec(ohos_media_data);

    *p_codec = codec;
    return PJ_SUCCESS;
}

static pj_status_t ohos_media_dealloc_codec(pjmedia_vid_codec_factory *factory, pjmedia_vid_codec *codec)
{
    ohos_media_codec_data *ohos_media_data;
    PJ_LOG(4, (THIS_FILE, "Encoder ohos_media_dealloc_codec destroy"));
    PJ_ASSERT_RETURN(codec, PJ_EINVAL);

    PJ_UNUSED_ARG(factory);

    ohos_media_data = (ohos_media_codec_data *)codec->codec_data;
    PJ_ASSERT_RETURN(ohos_media_data, PJ_EINVAL);

    if (ohos_media_data->enc) {
        ohos_media_data->enc->Release();
        ohos_media_data->enc = nullptr;
    }

    if (ohos_media_data->dec) {
        ohos_media_data->dec->Release();
        ohos_media_data->dec = nullptr;
    }
    pj_pool_release(ohos_media_data->pool);
    return PJ_SUCCESS;
}

static pj_status_t ohos_media_codec_init(pjmedia_vid_codec *codec, pj_pool_t *pool)
{
    PJ_ASSERT_RETURN(codec && pool, PJ_EINVAL);
    PJ_UNUSED_ARG(codec);
    PJ_UNUSED_ARG(pool);
    return PJ_SUCCESS;
}

static pj_status_t ohos_media_codec_open(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *codec_param)
{
    ohos_media_codec_data *ohos_media_data;
    pjmedia_vid_codec_param *param;
    pj_status_t status = PJ_SUCCESS;

    ohos_media_data = (ohos_media_codec_data *)codec->codec_data;
    ohos_media_data->prm = pjmedia_vid_codec_param_clone(ohos_media_data->pool, codec_param);
    param = ohos_media_data->prm;
    if (ohos_media_codec[ohos_media_data->codec_idx].open_codec) {
        status = ohos_media_codec[ohos_media_data->codec_idx].open_codec(ohos_media_data);
        if (status != PJ_SUCCESS)
            return status;
    }
    ohos_media_data->whole = (param->packing == PJMEDIA_VID_PACKING_WHOLE);
    status = configure_encoder(ohos_media_data);
    if (status != PJ_SUCCESS) {
        return PJMEDIA_CODEC_EFAILED;
    }
    status = configure_decoder(ohos_media_data);
    if (status != PJ_SUCCESS) {
        return PJMEDIA_CODEC_EFAILED;
    }
    if (ohos_media_data->dec_buf_size == 0) {
        ohos_media_data->dec_buf_size = (MAX_RX_WIDTH * MAX_RX_HEIGHT * MAGIC_THIRD >> MAGIC_ONE) + (MAX_RX_WIDTH);
    }
    ohos_media_data->dec_buf = (pj_uint8_t *)pj_pool_alloc(ohos_media_data->pool, ohos_media_data->dec_buf_size);
    /* Need to update param back after values are negotiated */
    pj_memcpy(codec_param, param, sizeof(*codec_param));
    return PJ_SUCCESS;
}

static pj_status_t ohos_media_codec_close(pjmedia_vid_codec *codec)
{
    PJ_LOG(4, (THIS_FILE, "Encoder ohos_media_codec_close start"));
    PJ_ASSERT_RETURN(codec, PJ_EINVAL);
    PJ_UNUSED_ARG(codec);
    return PJ_SUCCESS;
}

static pj_status_t ohos_media_codec_modify(pjmedia_vid_codec *codec, const pjmedia_vid_codec_param *param)
{
    PJ_LOG(4, (THIS_FILE, "Encoder ohos_media_codec_modify start"));
    PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);
    PJ_UNUSED_ARG(codec);
    PJ_UNUSED_ARG(param);

    return PJ_EINVALIDOP;
}

static pj_status_t ohos_media_codec_get_param(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *param)
{
    struct ohos_media_codec_data *ohos_media_data;
    PJ_LOG(4, (THIS_FILE, "Encoder ohos_media_codec_get_param start"));
    PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);

    ohos_media_data = (ohos_media_codec_data *)codec->codec_data;
    pj_memcpy(param, ohos_media_data->prm, sizeof(*param));

    return PJ_SUCCESS;
}

static pj_status_t ohos_media_codec_encode_begin(pjmedia_vid_codec *codec, const pjmedia_vid_encode_opt *opt,
                                                 const pjmedia_frame *input, unsigned out_size, pjmedia_frame *output,
                                                 pj_bool_t *has_more)
{
    struct ohos_media_codec_data *ohos_media_data;
    pj_status_t status;
    uint8_t *input_buf = NULL;
    OH_AVCodecBufferAttr attr;
    ohos::AvBufferInfo inputBuffer{0, nullptr};
    ohos::AvBufferInfo outputBuffer{0, nullptr};
    PJ_ASSERT_RETURN(codec && input && out_size && output && has_more, PJ_EINVAL);

    ohos_media_data = (ohos_media_codec_data *)codec->codec_data;
    ohos::VideoEncoder *encoder = ohos_media_data->enc.get();

    if (opt && opt->force_keyframe) {
        ohos_media_data->enc->RequestIFrame();
    }

    int ret = encoder->GetInputBuffer(inputBuffer, std::chrono::milliseconds(TIMEOUT));
    if (ret != ohos::ERRCODE_OK) {
        encoder->FreeInputData(inputBuffer.Index());
        if (ret == ohos::ERRCODE_TIMEOUT || ret == ohos::ERRCODE_QUEUE_EMPTY) {
            output->size = 0;
            output->type = PJMEDIA_FRAME_TYPE_NONE;
            *has_more = PJ_FALSE;
            return PJ_SUCCESS;
        }
        return PJMEDIA_CODEC_EFAILED;
    }

    input_buf = inputBuffer.AvBufferAddr();
    if (input_buf == nullptr) {
        encoder->FreeInputData(inputBuffer.Index());
        PJ_LOG(4, (THIS_FILE, "Invalid input buffer address"));
        return PJMEDIA_CODEC_EFAILED;
    }

    if (ohos_media_data->enc_stride_len == 0) {
        ohos_media_data->enc_stride_len = ohos_media_data->enc->GetFormatInfo(ohos::FormatType::FORMAT_TYPE_VIDEO_STRIDE);
    }

    int src_width = ohos_media_data->enc->format_->width;        // 原始宽度
    int src_height = ohos_media_data->enc->format_->height;      // 原始高度
    int dst_stride = ohos_media_data->enc_stride_len;            // 编码器步幅
    unsigned char *src_buffer = (unsigned char *)input->buf;     // 原始 YUV420 数据缓冲区
    unsigned char *dst_buffer = (unsigned char *)input_buf;      // 编码器目标缓冲区

    // 计算 Y、U、V 在原始数据和目标缓冲区中的偏移
    unsigned char *src_y = src_buffer;
    unsigned char *src_u = src_y + (src_width * src_height);
    unsigned char *src_v = src_u + (src_width / MAGIC_TWO * src_height / MAGIC_TWO);

    unsigned char *dst_y = dst_buffer;
    unsigned char *dst_u = dst_y + (dst_stride * src_height);
    unsigned char *dst_v = dst_u + (dst_stride / MAGIC_TWO * src_height / MAGIC_TWO);

    // 逐行拷贝 Y 分量
    for (int i = 0; i < src_height; i++) {
        memcpy(dst_y + i * dst_stride, src_y + i * src_width, src_width);
        // 注意：每行只 copies src_width 字节，剩余部分会自动填充 0
    }

    // 逐行拷贝 U 分量
    for (int i = 0; i < src_height / MAGIC_TWO; i++) {
        memcpy(dst_u + i * (dst_stride / MAGIC_TWO), src_u + i * (src_width / MAGIC_TWO), src_width / MAGIC_TWO);
    }

    // 逐行拷贝 V 分量
    for (int i = 0; i < src_height / MAGIC_TWO; i++) {
        memcpy(dst_v + i * (dst_stride / MAGIC_TWO), src_v + i * (src_width / MAGIC_TWO), src_width / MAGIC_TWO);
    }

    attr = *inputBuffer.Attr();
    attr.pts = input->timestamp.u64;
    attr.size = inputBuffer.AvBufferSize();
    inputBuffer.Attr(&attr);
    encoder->FreeInputData(inputBuffer.Index());

    ret = encoder->GetOutputBuffer(outputBuffer, std::chrono::milliseconds(TIMEOUT));
    if (ret != ohos::ERRCODE_OK) {
        goto on_return;
    }
    ohos_media_data->enc_buf_info = *outputBuffer.Attr();

    ohos_media_data->enc_processed = 0;
    ohos_media_data->enc_frame_whole = outputBuffer.AvBufferAddr();
    ohos_media_data->enc_output_buf_idx = outputBuffer.Index();
    ohos_media_data->enc_frame_size = ohos_media_data->enc_buf_info.size;

    if (ohos_media_codec[ohos_media_data->codec_idx].process_encode) {
        pj_status_t status;
        status = ohos_media_codec[ohos_media_data->codec_idx].process_encode(ohos_media_data);
        if (status != PJ_SUCCESS) {
            goto on_return;
        }
    }

    if (ohos_media_data->enc_buf_info.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) {
        output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
    }

    if (ohos_media_data->whole) {
        unsigned payload_size = 0;
        unsigned start_data = 0;
        *has_more = PJ_FALSE;

        if ((ohos_media_data->prm->enc_fmt.id == PJMEDIA_FORMAT_H264) &&
            (ohos_media_data->enc_buf_info.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME)) {
            h264_codec_data *h264_data = (h264_codec_data *)ohos_media_data->ex_data;
            start_data = h264_data->enc_sps_pps_len;
            pj_memcpy(output->buf, h264_data->enc_sps_pps_buf, h264_data->enc_sps_pps_len);
        }

        payload_size = ohos_media_data->enc_buf_info.size + start_data;

        if (payload_size > out_size)
            goto on_return;

        output->type = PJMEDIA_FRAME_TYPE_VIDEO;
        output->size = payload_size;
        output->timestamp = input->timestamp;
        pj_memcpy((pj_uint8_t *)output->buf + start_data, ohos_media_data->enc_frame_whole,
                  ohos_media_data->enc_buf_info.size);

        ohos_media_data->enc->FreeOutputData(outputBuffer.Index());
        return PJ_SUCCESS;
    }
    return ohos_media_codec_encode_more(codec, out_size, output, has_more);

on_return:
    output->size = 0;
    output->type = PJMEDIA_FRAME_TYPE_NONE;
    *has_more = PJ_FALSE;
    encoder->FreeOutputData(outputBuffer.Index());
    return PJ_SUCCESS;
}

static pj_status_t ohos_media_codec_encode_more(pjmedia_vid_codec *codec, unsigned out_size, pjmedia_frame *output,
                                                pj_bool_t *has_more)
{
    struct ohos_media_codec_data *ohos_media_data;
    pj_status_t status = PJ_SUCCESS;
    PJ_ASSERT_RETURN(codec && out_size && output && has_more, PJ_EINVAL);

    ohos_media_data = (ohos_media_codec_data *)codec->codec_data;

    status = ohos_media_codec[ohos_media_data->codec_idx].encode_more(ohos_media_data, out_size, output, has_more);
    if (!(*has_more)) {
        ohos_media_data->enc->FreeOutputData(ohos_media_data->enc_output_buf_idx);
    }
    return status;
}

void save_nv21_to_yuv420(char* nv21_data, unsigned char* buffer, int width, int height, int stride)
{
    int y_size = width * height;
    int uv_size = (width * height) / MAGIC_FOUR;
    int uv_height = height / MAGIC_TWO;
    int uv_width = width / MAGIC_TWO;

    // 输出缓冲区的指针
    unsigned char* y_out = buffer;                  // Y平面起始位置
    unsigned char* u_out = buffer + y_size;         // U平面起始位置
    unsigned char* v_out = buffer + y_size + uv_size; // V平面起始位置

    // 1. 写入Y数据，去掉步幅填充
    for (int h = 0; h < height; h++) {
        char* y_row = nv21_data + h * stride;
        memcpy(y_out + h * width, y_row, width);
    }

    // 2. 分离并写入U和V数据
    char* uv_start = nv21_data + stride * height; // UV数据起始位置
    for (int h = 0; h < uv_height; h++) {
        char* uv_row = uv_start + h * stride;
        for (int w = 0; w < uv_width; w++) {
            u_out[h * uv_width + w] = uv_row[w * MAGIC_TWO + MAGIC_ONE]; // U (奇数字节)
            v_out[h * uv_width + w] = uv_row[w * MAGIC_TWO];     // V (偶数字节)
        }
    }
}

static pj_status_t ohos_media_decode(pjmedia_vid_codec *codec,
    struct ohos_media_codec_data *ohos_media_data,
    pj_uint8_t *input_buf, unsigned buf_size,
    int buf_flag, pj_timestamp *input_ts,
    pj_bool_t write_output, pjmedia_frame *output)
{
    pj_status_t status = PJ_SUCCESS;
    if (ohos_media_data->format_changed) {
        ohos_media_data->height = ohos_media_data->dec->GetFormatInfo(ohos::FormatType::FORMAT_TYPE_IMAGE_HEIGHT);
        ohos_media_data->width = ohos_media_data->dec->GetFormatInfo(ohos::FormatType::FORMAT_TYPE_IMAGE_WIDTH);
        ohos_media_data->stride = ohos_media_data->dec->GetFormatInfo(ohos::FormatType::FORMAT_TYPE_VIDEO_STRIDE);
    
        if (ohos_media_data->width != ohos_media_data->prm->dec_fmt.det.vid.size.w ||
            ohos_media_data->height != ohos_media_data->prm->dec_fmt.det.vid.size.h) {
            pjmedia_event event;

            ohos_media_data->prm->dec_fmt.det.vid.size.w = ohos_media_data->width;
            ohos_media_data->prm->dec_fmt.det.vid.size.h = ohos_media_data->height;

            PJ_LOG(4,(THIS_FILE, "Frame size changed to %dx%d",
                      ohos_media_data->prm->dec_fmt.det.vid.size.w,
                      ohos_media_data->prm->dec_fmt.det.vid.size.h));

            /* Broadcast format changed event */
            pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED,
                               NULL, codec);
            event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING;
            pjmedia_format_copy(&event.data.fmt_changed.new_fmt,
                                &ohos_media_data->prm->dec_fmt);
            pjmedia_event_publish(NULL, codec, &event,
                                  PJMEDIA_EVENT_PUBLISH_DEFAULT);
        }
        ohos_media_data->format_changed = PJ_FALSE;
    }

    if ((ohos_media_data->dec_input_buf_max_size > 0) &&
        (ohos_media_data->dec_input_buf_len + buf_size > ohos_media_data->dec_input_buf_max_size)) {
        int ret = ohos_media_data->dec->PushInputData(ohos_media_data->dec_input_buf,
                ohos_media_data->dec_input_buf_len, input_ts->u32.lo, 0, std::chrono::milliseconds(8));
        if (ret != ohos::ERRCODE_OK) {
            PJ_LOG(1, (THIS_FILE, "push data failed, ret: %d", ret));
            return PJ_FALSE;
        }
    }

    pj_memcpy(ohos_media_data->dec_input_buf + ohos_media_data->dec_input_buf_len, input_buf, buf_size);
    ohos_media_data->dec_input_buf_len += buf_size;

    if (!write_output)
        return status;

    int ret = ohos_media_data->dec->PushInputData(ohos_media_data->dec_input_buf, ohos_media_data->dec_input_buf_len,
            input_ts->u32.lo, 0, std::chrono::milliseconds(TIMEOUT));
    if (ret != ohos::ERRCODE_OK) {
        PJ_LOG(1, (THIS_FILE, "push data failed, ret: %d", ret));
    }

    ohos::AvBufferInfo info {0, nullptr};
    ret = ohos_media_data->dec->GetOutputData(info, std::chrono::milliseconds(8));
    if (ret != ohos::ERRCODE_OK) {
        PJ_LOG(1, (THIS_FILE, "get data failed, ret: %d", ret));
        return PJ_FALSE;
    }

    const OH_AVCodecBufferAttr *attr = info.Attr();
    if (attr == nullptr) {
        ohos_media_data->dec->FreeOutputData(info.Index());
        return PJMEDIA_CODEC_EFRMTOOSHORT;
    }

    uint8_t *addr = info.AvBufferAddr();
    if (addr == nullptr) {
        ohos_media_data->dec->FreeOutputData(info.Index());
        return PJMEDIA_CODEC_EFRMTOOSHORT;
    }

    save_nv21_to_yuv420((char*)addr, (unsigned char*)output->buf, ohos_media_data->width, ohos_media_data->height,
        ohos_media_data->stride);
    ohos_media_data->dec->FreeOutputData(info.Index());

    if (!ohos_media_data->dec_has_output_frame) {
        output->type = PJMEDIA_FRAME_TYPE_VIDEO;
        output->size = ohos_media_data->width * ohos_media_data->height * 3 / 2;
        output->timestamp = *input_ts;
        ohos_media_data->dec_has_output_frame = PJ_TRUE;
    }
    return status;
}

static pj_status_t ohos_media_codec_decode(pjmedia_vid_codec *codec, pj_size_t count, pjmedia_frame packets[],
    unsigned out_size, pjmedia_frame *output)
{
    struct ohos_media_codec_data *ohos_media_data;
    pj_status_t status = PJ_EINVAL;

    PJ_ASSERT_RETURN(codec && count && packets && out_size && output, PJ_EINVAL);
    PJ_ASSERT_RETURN(output->buf, PJ_EINVAL);

    ohos_media_data = (ohos_media_codec_data*) codec->codec_data;
    ohos_media_data->dec_has_output_frame = PJ_FALSE;
    ohos_media_data->dec_input_buf_len = 0;

    if (ohos_media_codec[ohos_media_data->codec_idx].decode) {
        status = ohos_media_codec[ohos_media_data->codec_idx].decode(codec, count,
                                                           packets, out_size,
                                                           output);
    }
    if (status != PJ_SUCCESS) {
        PJ_LOG(3, (THIS_FILE, "decode failed"));
        return status;
    }

    if (!ohos_media_data->dec_has_output_frame) {
        pjmedia_event event;

        /* Broadcast missing keyframe event */
        pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING,
                           &packets[0].timestamp, codec);
        pjmedia_event_publish(NULL, codec, &event,
                              PJMEDIA_EVENT_PUBLISH_DEFAULT);

        PJ_LOG(4,(THIS_FILE, "Decoder couldn't produce output frame"));

        output->type = PJMEDIA_FRAME_TYPE_NONE;
        output->size = 0;
        output->timestamp = packets[0].timestamp;
    }
    return PJ_SUCCESS;
}

static pj_status_t open_h264(ohos_media_codec_data *ohos_media_data)
{
    pj_status_t status;
    pjmedia_vid_codec_param *param = ohos_media_data->prm;
    pjmedia_h264_packetizer_cfg pktz_cfg;
    pjmedia_vid_codec_h264_fmtp h264_fmtp;
    h264_codec_data *h264_data;

    /* Parse remote fmtp */
    pj_bzero(&h264_fmtp, sizeof(h264_fmtp));
    status = pjmedia_vid_codec_h264_parse_fmtp(&param->enc_fmtp, &h264_fmtp);
    if (status != PJ_SUCCESS)
        return status;

    /* Apply SDP fmtp to format in codec param */
    if (!param->ignore_fmtp) {
        status = pjmedia_vid_codec_h264_apply_fmtp(param);
        if (status != PJ_SUCCESS)
            return status;
    }
    h264_data = PJ_POOL_ZALLOC_T(ohos_media_data->pool, h264_codec_data);
    if (!h264_data)
        return PJ_ENOMEM;

    pj_bzero(&pktz_cfg, sizeof(pktz_cfg));
    pktz_cfg.mtu = param->enc_mtu;
    pktz_cfg.unpack_nal_start = 4;

    /* Packetization mode */
    if (h264_fmtp.packetization_mode == 0) {
        pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; 
    } else if (h264_fmtp.packetization_mode == 1) {
        pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
    } else {
        return PJ_ENOTSUP;
    }

    pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
    status = pjmedia_h264_packetizer_create(ohos_media_data->pool, &pktz_cfg, &h264_data->pktz);
    if (status != PJ_SUCCESS) {
        return status;
    }

    ohos_media_data->ex_data = h264_data;
    ohos_media_data->dec_buf_size = (MAX_RX_WIDTH * MAX_RX_HEIGHT * 3 >> 1) + (MAX_RX_WIDTH);

    /* If available, use the "sprop-parameter-sets" fmtp from remote SDP
     * to create the decoder.
     */
    if (h264_fmtp.sprop_param_sets_len) {
        const pj_uint8_t start_code[MAGIC_THIRD] = {0, 0, 1};
        const int code_size = PJ_ARRAY_SIZE(start_code);
        const pj_uint8_t med_start_code[MAGIC_FOUR] = {0, 0, 0, 1};
        const int med_code_size = PJ_ARRAY_SIZE(med_start_code);
        unsigned i, j;

        for (i = h264_fmtp.sprop_param_sets_len - code_size; i >= code_size; i--) {
            for (j = 0; j < code_size; j++) {
                if (h264_fmtp.sprop_param_sets[i + j] != start_code[j]) {
                    break;
                }
            }
        }

        if (i >= code_size) {
            h264_data->dec_sps_len = i + med_code_size - code_size;
            h264_data->dec_pps_len = h264_fmtp.sprop_param_sets_len + med_code_size - code_size - i;
            h264_data->dec_sps_buf = (pj_uint8_t *)pj_pool_alloc(ohos_media_data->pool, h264_data->dec_sps_len);
            h264_data->dec_pps_buf = (pj_uint8_t *)pj_pool_alloc(ohos_media_data->pool, h264_data->dec_pps_len);

            pj_memcpy(h264_data->dec_sps_buf, med_start_code, med_code_size);
            pj_memcpy(h264_data->dec_sps_buf + med_code_size, &h264_fmtp.sprop_param_sets[code_size],
                      h264_data->dec_sps_len - med_code_size);
            pj_memcpy(h264_data->dec_pps_buf, med_start_code, med_code_size);
            pj_memcpy(h264_data->dec_pps_buf + med_code_size, &h264_fmtp.sprop_param_sets[i + code_size],
                      h264_data->dec_pps_len - med_code_size);
        }
    }
    return status;
}

static pj_status_t process_encode_h264(ohos_media_codec_data *ohos_media_data)
{
    pj_status_t status = PJ_SUCCESS;
    h264_codec_data *h264_data;
    h264_data = (h264_codec_data *)ohos_media_data->ex_data;
    if (ohos_media_data->enc_buf_info.flags & AVCODEC_BUFFER_FLAGS_CODEC_DATA) {
        /*
         * Config data or SPS+PPS. Update the SPS and PPS buffer,
         * this will be sent later when sending Keyframe.
         */
        h264_data->enc_sps_pps_len = PJ_MIN(ohos_media_data->enc_buf_info.size, sizeof(h264_data->enc_sps_pps_buf));
        pj_memcpy(h264_data->enc_sps_pps_buf, ohos_media_data->enc_frame_whole, h264_data->enc_sps_pps_len);
        ohos_media_data->enc->FreeOutputData(ohos_media_data->enc_output_buf_idx);
        return PJ_EIGNORED;
    }
    if (ohos_media_data->enc_buf_info.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) {
        h264_data->enc_sps_pps_ex = PJ_TRUE;
        ohos_media_data->enc_frame_size = h264_data->enc_sps_pps_len;
    } else {
        h264_data->enc_sps_pps_ex = PJ_FALSE;
    }
    return status;
}

static pj_status_t encode_more_h264(ohos_media_codec_data *ohos_media_data, unsigned out_size, pjmedia_frame *output,
                                    pj_bool_t *has_more)
{
    const pj_uint8_t *payload;
    pj_size_t payload_len;
    pj_status_t status;
    pj_uint8_t *data_buf = NULL;
    h264_codec_data *h264_data;
    h264_data = (h264_codec_data *)ohos_media_data->ex_data;
    if (h264_data->enc_sps_pps_ex) {
        data_buf = h264_data->enc_sps_pps_buf;
    } else {
        data_buf = ohos_media_data->enc_frame_whole;
    }
    /* We have outstanding frame in packetizer */
    status = pjmedia_h264_packetize(h264_data->pktz, data_buf, ohos_media_data->enc_frame_size,
                                    &ohos_media_data->enc_processed, &payload, &payload_len);
    if (status != PJ_SUCCESS) {
        /* Reset */
        ohos_media_data->enc_frame_size = ohos_media_data->enc_processed = 0;
        *has_more = (ohos_media_data->enc_processed < ohos_media_data->enc_frame_size);

        PJ_PERROR(3, (THIS_FILE, status, "pjmedia_h264_packetize() error"));
        return status;
    }
    PJ_ASSERT_RETURN(payload_len <= out_size, PJMEDIA_CODEC_EFRMTOOSHORT);

    output->type = PJMEDIA_FRAME_TYPE_VIDEO;
    pj_memcpy(output->buf, payload, payload_len);
    output->size = payload_len;

    if (ohos_media_data->enc_processed >= ohos_media_data->enc_frame_size) {
        h264_codec_data *h264_data = (h264_codec_data *)ohos_media_data->ex_data;
        if (h264_data->enc_sps_pps_ex) {
            *has_more = PJ_TRUE;
            h264_data->enc_sps_pps_ex = PJ_FALSE;
            ohos_media_data->enc_processed = 0;
            ohos_media_data->enc_frame_size = ohos_media_data->enc_buf_info.size;
        } else {
            *has_more = PJ_FALSE;
        }
    } else {
        *has_more = PJ_TRUE;
    }
    return PJ_SUCCESS;
}

static pj_status_t decode_h264(pjmedia_vid_codec *codec, pj_size_t count, pjmedia_frame packets[], unsigned out_size,
                               pjmedia_frame *output)
{

    struct ohos_media_codec_data *ohos_media_data;
    const pj_uint8_t start_code[] = { 0, 0, 0, 1 };
    const int code_size = PJ_ARRAY_SIZE(start_code);
    unsigned buf_pos, whole_len = 0;
    unsigned i, frm_cnt;
    pj_status_t status;

    PJ_ASSERT_RETURN(codec && count && packets && out_size && output,
                     PJ_EINVAL);
    PJ_ASSERT_RETURN(output->buf, PJ_EINVAL);

    ohos_media_data = (ohos_media_codec_data*) codec->codec_data;

    /*
     * Step 1: unpacketize the packets/frames
     */
    whole_len = 0;
    if (ohos_media_data->whole) {
        for (i=0; i<count; ++i) {
            if (whole_len + packets[i].size > ohos_media_data->dec_buf_size) {
                PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [1]"));
                return PJMEDIA_CODEC_EFRMTOOSHORT;
            }

            pj_memcpy(ohos_media_data->dec_buf + whole_len,
                       (pj_uint8_t*)packets[i].buf,
                       packets[i].size);
            whole_len += packets[i].size;
        }

    } else {
        h264_codec_data *h264_data = (h264_codec_data *)ohos_media_data->ex_data;
        for (i=0; i<count; ++i) {

            if (whole_len + packets[i].size + code_size >
                ohos_media_data->dec_buf_size)
            {
                PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [2]"));
                return PJMEDIA_CODEC_EFRMTOOSHORT;
            }

            status = pjmedia_h264_unpacketize( h264_data->pktz,
                                               (pj_uint8_t*)packets[i].buf,
                                               packets[i].size,
                                               ohos_media_data->dec_buf,
                                               ohos_media_data->dec_buf_size,
                                               &whole_len);
            if (status != PJ_SUCCESS) {
                PJ_PERROR(4,(THIS_FILE, status, "Unpacketize error"));
                continue;
            }
        }
    }

    if (whole_len + code_size > ohos_media_data->dec_buf_size ||
        whole_len <= code_size + 1)
    {
        PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow or unpacketize error "
                             "size: %d, buffer: %d", whole_len,
                             ohos_media_data->dec_buf_size));
        return PJMEDIA_CODEC_EFRMTOOSHORT;
    }

    /* Dummy NAL sentinel */
    pj_memcpy(ohos_media_data->dec_buf + whole_len, start_code, code_size);

    /*
     * Step 2: parse the individual NAL and give to decoder
     */
    buf_pos = 0;
    for ( frm_cnt=0; ; ++frm_cnt) {
        pj_uint32_t frm_size;
        pj_bool_t write_output = PJ_FALSE;
        unsigned char *start;

        for (i = code_size - 1; buf_pos + i < whole_len; i++) {
            if (ohos_media_data->dec_buf[buf_pos + i] == 0 &&
                ohos_media_data->dec_buf[buf_pos + i + 1] == 0 &&
                ohos_media_data->dec_buf[buf_pos + i + 2] == 0 &&
                ohos_media_data->dec_buf[buf_pos + i + 3] == 1)
            {
                break;
            }
        }

        frm_size = i;
        start = ohos_media_data->dec_buf + buf_pos;
        write_output = (buf_pos + frm_size >= whole_len);
        status = ohos_media_decode(codec, ohos_media_data, start, frm_size, 0,
                              &packets[0].timestamp, write_output, output);
        if (status != PJ_SUCCESS)
            return status;

        if (write_output)
            break;

        buf_pos += frm_size;
    }
    return PJ_SUCCESS;
}

#endif /* PJMEDIA_HAS_OHOS_AVCODEC */
